﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using Styx;
using Styx.Combat.CombatRoutine;
using Styx.Helpers;
using Styx.Logic;
using Styx.Logic.Combat;
using Styx.Logic.Pathing;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;

namespace RestorationIce
{
   
    partial class RestoIce
    {  
        #region Helpers
        public void healthmoniter()
        {           
                List<double> myhealthtrack = new List<double>();
                List<double> tankhealthtrack = new List<double>();
                List<double> myhealthcalc1 = new List<double>();
                List<double> tankhealthcalc1 = new List<double>();
                if (!Me.Combat)
                {
                    myhealthtrack.Clear();
                    myhealthcalc1.Clear();
                    tankhealthtrack.Clear();
                    tankhealthcalc1.Clear();
                    Array.Clear(myhealthcalc,0, myhealthcalc.Length);
                    Array.Clear(tankhealthcalc,0,tankhealthcalc.Length);
                    myhealthcentlosspersecond = 0;
                    tankhealthcentlosspersecond = 0;
                    counter = 0;
                    counter2 = 0;
                }
                if (Me.Combat)
                {
                    myhealthtrack.Add(Me.HealthPercent);
                    tankhealthtrack.Add(tank.HealthPercent);

                    slog(Color.Navy, "Inside the Health Moniter Code!");
                    if (counter > 3)
                    {
                        for (int i = 0; i < myhealthtrack.Count() - 1; i++)
                        {
                            myhealthcalc1.Add(myhealthtrack[i] - myhealthtrack[(i + 1)]);
                            myhealthcalc[counter2] = myhealthcalc1.Average();
                        }

                        for (int i = 0; i < tankhealthtrack.Count() - 1; i++)
                        {
                            tankhealthcalc1.Add(tankhealthtrack[i] - tankhealthtrack[(i + 1)]);
                            tankhealthcalc[counter2] = tankhealthcalc1.Average();
                        }
                        counter2++;
                        if (counter2 >= 5) { counter2 = 0; }

                        myhealthcentlosspersecond = GetMean(myhealthcalc);
                        tankhealthcentlosspersecond = GetMean(tankhealthcalc);
                        slog(Color.Navy, "%-/s Me: {0} Tank: {1}", myhealthcentlosspersecond, tankhealthcentlosspersecond);
                        myhealthtrack.Clear();
                        tankhealthtrack.Clear();
                        counter = -1;
                    }
                    counter++;

                    healthmon.Reset();
                    healthmon.Start();
                }
        }       
        public static double GetMean(double[] Values)
        {
            double mean = 0;
            foreach (double average in Values)
            {
                mean += average;
            }
            mean /= Values.Length;
            return mean;
        }
        private bool Behaviour()
        {
            actualBehaviour = Me.PvPState.ToString();
            if ((actualBehaviour == "None") && lastBehaviour == null)
            {
                usedBehaviour = "Starting";
            }
            else if ((actualBehaviour == "None") && !Me.IsInInstance && !Me.IsInParty && !Me.IsInRaid)
            {
                usedBehaviour = "Solo";
            }
            else if ((actualBehaviour == "None") && !Me.IsInInstance)
            {
                usedBehaviour = "Party or Raid";
            }
            else if ((actualBehaviour == "FFAPVP") && Me.IsInInstance)
            {
                usedBehaviour = "Arena";
            }
            else if (!Me.IsInInstance && actualBehaviour == "PVP")
            {
                usedBehaviour = "World PVP";
            }
            else if (Me.IsInInstance && actualBehaviour == "PVP")
            {
                usedBehaviour = "Battleground";
            }
            else if ((Me.IsInInstance) && (Me.IsInParty) && actualBehaviour != "FFAPVP" && actualBehaviour != "PVP")
            {
                usedBehaviour = "Dungeon";
            }
            else if ((Me.IsInInstance) && (Me.IsInRaid) && actualBehaviour != "FFAPVP" && actualBehaviour != "PVP")
            {
                usedBehaviour = "Raid";
            }
            else
            {
                usedBehaviour = "Really?, Seriously WTF are you doing?";
            }
            if (lastBehaviour == usedBehaviour && usedBehaviour != null)
            {
                return false;
            }
            slog(Color.HotPink, "{0}", usedBehaviour);
            lastBehaviour = usedBehaviour;
            return true;
        }
        private string DeUnicodify(string s)
        {
            StringBuilder sb = new StringBuilder();
            byte[] bytes = Encoding.UTF8.GetBytes(s);
            foreach (byte b in bytes)
            {
                if (b != 0)
                    sb.Append("\\" + b);
            }
            return sb.ToString();
        }
        private bool MoveTo(WoWUnit u)
        {
            if (rng == null)
            {
                rng = new Random();
            }
            if (!Me.IsMoving && u != null)//&& u.Distance > 35)
            {
                Navigator.MoveTo(WoWMathHelper.CalculatePointAtSide(u.Location, u.Rotation, rng.Next(10), rng.Next(2) == 1));
                return true;
            }
            else
            {
                return false;
            }
        }
        private double Round(double d)
        {
            return Math.Round(d, 2);
        }
        #region Cast
        private bool CanCast(string spell, WoWUnit target)
        {
            return SpellManager.CanCast(spell, target);
        }
        private bool CanCast(string spell)
        {
            return SpellManager.CanCast(spell);
        }
        private bool Cast(string spell, WoWUnit target, string reason)
        {
            return internal_Cast(spell, target, 80, "Default", reason);
        }
        private bool Cast(string spell, string type, string reason)
        {
            return internal_Cast(spell, Me, 5, type, reason);
        }
        private bool Cast(string spell, WoWUnit target, string type, string reason)
        {
            return internal_Cast(spell, target, 80, type, reason);
        }
        private bool Cast(string spell, string reason)
        {
            return internal_Cast(spell, Me, 5, "Default", reason);
        }
        private bool Cast(string spell, WoWUnit target, int max_distance, string reason)
        {
            return internal_Cast(spell, target, max_distance, "Default", reason);
        }
        private bool Cast(string spell, WoWUnit target, int max_distance, string type, string reason)
        {
            return internal_Cast(spell, target, max_distance, type, reason);
        }
        private bool internal_Cast(string spell, WoWUnit target, int max_distance, string type, string reason)
        {
            if (target != null && target.IsValid && !target.Dead && target.InLineOfSight && target.Distance < (double)max_distance && CanCast(spell, target))
            {
                if (target == Me)
                {
                    lastCast = null;
                    formatslog(type, reason, spell);
                    return SpellManager.Cast(spell, Me);
                }
                else
                {
                    lastCast = target;
                    formatslog(type, reason, spell, target);
                    return SpellManager.Cast(spell, target);

                }
            }
            else if (target != null && target.IsValid && !target.Dead && target.InLineOfSight && target.Distance > (double)max_distance)
            {
                slog(Color.DarkRed, reason + ": but Target {0} at {1} max spell distance {2} Out of range! Move to him?", privacyname(target), Round(target.Distance), max_distance);
                //Blacklist.Add(target, new TimeSpan(0, 0, 5));
                return false;
            }
            else if (target != null && target.IsValid && !target.Dead && !target.InLineOfSight)
            {
                slog(Color.DarkRed, reason + ": but Target {0} at {1} max spell distance {2} Out of LoS! Move to him?", privacyname(target), Round(target.Distance), max_distance);
                //Blacklist.Add(target, new TimeSpan(0, 0, 5));
                return false;

            }
            return false;
        }
        private bool castaress(string spell, WoWUnit target, int max_distance, string type, string reason)
        {
            if (target != null && target.IsValid && target.Dead && target.InLineOfSight && target.Distance < (double)max_distance && CanCast(spell, target))
            {
                lastCast = target;
                formatslog(type, reason, spell, target);
                return SpellManager.Cast(spell, target);
            }
            else if (target != null && target.IsValid && target.Dead && target.InLineOfSight && target.Distance > (double)max_distance)
            {
                slog(Color.DarkRed, reason + ": but Target {0} at {1} max spell distance {2} Out of range! Move to him?", privacyname(target), Round(target.Distance), max_distance);
                //Blacklist.Add(target, new TimeSpan(0, 0, 5));
                return false;
            }
            else if (target != null && target.IsValid && target.Dead && !target.InLineOfSight)
            {
                slog(Color.DarkRed, reason + ": but Target {0} at {1} max spell distance {2} Out of LoS! Move to him?", privacyname(target), Round(target.Distance), max_distance);
                //Blacklist.Add(target, new TimeSpan(0, 0, 5));
                return false;

            }
            return false;
        }
        #endregion
        #region Tank
        private bool IsTank(WoWPlayer p)
        {
            if (p != null)
            {
                return Lua.GetReturnValues("return UnitGroupRolesAssigned('" + DeUnicodify(p.Name) + "')").First() == "TANK";
            }
            else return false;
        }
        private WoWPlayer tankfromlua()
        {
            if (InRaid())
            {
                foreach (WoWPlayer p in Me.RaidMembers)
                {
                    if (IsTank(p))
                    {
                        return p;
                    }
                }
            }
            else
            {
                foreach (WoWPlayer p in Me.PartyMembers)
                {
                    if (IsTank(p))
                    {
                        return p;
                    }
                }
            }
            return null;
        }
        private WoWPlayer GetTank()
        {
            WoWPlayer luatank = tankfromlua();
            if (unitcheck(RaFHelper.Leader) == 1)
            {
                if (fallbacktank != RaFHelper.Leader)
                {
                    slog(Color.DarkRed, "Selecting the tank {0} from LazyRaider!", privacyname(RaFHelper.Leader));
                }
                fallbacktank = RaFHelper.Leader;
                return RaFHelper.Leader;
            }
            else if (unitcheck(luatank) == 1)
            {
                slog(Color.DarkRed, "You did not selected anyone as tank, also {0} as the Role of tank, selecting him as tank", privacyname(luatank));
                RaFHelper.SetLeader(luatank);
                fallbacktank = luatank;
                return luatank;
            }
            else if (unitcheck(fallbacktank) == 1)
            {
                slog(Color.DarkRed, "mm.. we are using the fallbacktank {0} not that good.. please report this", privacyname(fallbacktank));
                return fallbacktank;
            }
            else
            {
                if (unitcheck(Me) == 1)
                {
                    if (usedBehaviour == "Raid" || usedBehaviour == "Dungeon" || usedBehaviour == "Party or Raid")
                    {
                        slog(Color.DarkRed, "We are in dungeon or raid but no valid tank is found, i'm tanking and that's not good at all! Is tank dead? if not select a tank from lazyraider or perform a RoleCheck!");
                    }
                    return Me;
                }
                else
                {
                    slog(Color.DarkRed, "Noone is a valid tank, even Myself, CC is in PAUSE");
                    return null;
                }
            }
        }                
        #endregion
        #region Buff
        private bool GotBuff(string name)
        {
            return GotBuff(name, Me);
        }
        private bool GotBuff(string name, WoWUnit u)
        {
            return u.ActiveAuras.ContainsKey(name);
        }                
        #endregion
        #region Target
        private WoWPlayer GetHealTarget()
        {
            return (from unit in ObjectManager.GetObjectsOfType<WoWPlayer>(true, true)
                    orderby unit.HealthPercent ascending
                    where !Blacklist.Contains(unit.Guid, true)  //do not heal a blacklisted target
                    where !unit.Dead
                    where !unit.IsGhost
                    where (unit.IsInMyPartyOrRaid || unit.IsMe)
                    where unit.Distance < 40
                    where unit.HealthPercent < 95
                    where !unit.Auras.ContainsKey("Bloodletting")
                    select unit).FirstOrDefault();

        }
        private WoWPlayer PartyCombat()
        {
            return (from unit in ObjectManager.GetObjectsOfType<WoWPlayer>(true, true)
                    orderby unit.Distance descending
                    where !unit.Dead
                    where !unit.IsGhost
                    where unit.IsInMyPartyOrRaid
                    where unit.Distance < 100
                    where unit.Combat
                    select unit).FirstOrDefault();

        }
        private IEnumerable<WoWPlayer> GetResurrectTargets()
        {
            return (from unit in ObjectManager.GetObjectsOfType<WoWPlayer>(false, false)
                    orderby unit.Distance ascending
                    where unit.Dead
                    where unit.IsInMyPartyOrRaid
                    where !unit.IsGhost
                    where unit.Distance < 100
                    select unit);
        }
        private WoWPlayer GetHealTarget(int distance, int maxhealt)
        {
            return (from unit in ObjectManager.GetObjectsOfType<WoWPlayer>(true, true)
                    orderby unit.HealthPercent ascending
                    where !unit.Dead
                    where !unit.IsGhost
                    where unit.Distance < distance
                    where (unit.IsInMyPartyOrRaid || unit.IsMe)
                    where unit.HealthPercent < maxhealt
                    where !unit.Auras.ContainsKey("Bloodletting")
                    select unit).FirstOrDefault();
        }
        private WoWPlayer GetRemoveCorruptionTarget()
        {
            return (from unit in ObjectManager.GetObjectsOfType<WoWPlayer>(true, true)
                    orderby unit.HealthPercent ascending
                    where !unit.Dead
                    where !unit.IsGhost
                    where unit.Distance < 80
                    where (unit.IsInMyPartyOrRaid || unit.IsMe)
                    where NeedsRemoveCorruption(unit)
                    select unit).FirstOrDefault();
        }
        #endregion
        #region Checks
        private int unitcheck(WoWUnit unit)
        {
            if (unit == null)
            {
                return -1;
            }
            else if (!unit.IsValid)
            {
                return -2;
            }
            else if (unit.Dead)
            {
                return -3;
            }
            return 1;
        }
        private int unitcheck(WoWPlayer unit)
        {
            if (unit == null)
            {
                return -1;
            }
            else if (!unit.IsValid)
            {
                return -2;
            }
            else if (unit.Dead)
            {
                return -3;
            }
            else if (unit.IsGhost)
            {
                return -4;
            }
            return 1;

        }
        private void checkthetank()
        {
            if (unitcheck(tank) < 0)
            {
                if (unitcheck(RaFHelper.Leader) < 0)
                {
                    if (unitcheck(fallbacktank) < 0)
                    {
                        if (unitcheck(Me) < 0)
                        {
                            slog(Color.DarkRed, "Sorry we tryed everything, no valid tank, no help from LazyRaider, no valid fallback tank, even I'm not a valid tank.. i'm giving up, CC is in Pause");
                            return;
                        }
                        else
                        {
                            if ((Me.IsInRaid || Me.IsInParty) && usedBehaviour != "Battleground")
                            {
                                slog(Color.DarkRed, "Noone else is a valid tank, selecting me as tank, PLS select a tank from LazyRaider configuration!");
                            }
                            tank = Me;
                        }
                    }
                    else
                    {
                        slog(Color.DarkRed, "Actual tank is not valid! LazyRaider is not helping! using the Fallbacktank {0} PLS select a tank from LazyRaider configuration!", privacyname(fallbacktank));
                        tank = fallbacktank;
                    }
                }
                else
                {
                    slog(Color.DarkRed, "Acual tankk is not valid! using LazyRaider tank as tank from now on! tank is now {0}", privacyname(RaFHelper.Leader));
                    tank = RaFHelper.Leader;
                }
            }
            fallbacktank = tank;
        }
        public bool InRaid()
        {
            return Me.RaidMembers.Count > 0;
        }
        public bool InParty()
        {
            return Me.PartyMember1 != null;
        }
        #endregion
        #region Logging
        private void slog(string format, params object[] args)
        {
            Logging.Write(format, args);
        }
        private void slog(System.Drawing.Color color, string format, params object[] args)
        {
            Logging.Write(color, format, args);
        }
        private void slog(System.Drawing.Color color, string format)
        {
            Logging.Write(color, format);
        }
        private void formatslog(string type, string reason, string spell, WoWUnit target)
        {
            System.Drawing.Color textcolor;
            switch (type)
            {
                case "Heal":
                    textcolor = Color.SpringGreen;
                    break;
                case "RemoveCorruption":
                    textcolor = Color.Fuchsia;
                    break;
                case "Buff":
                    textcolor = Color.DarkOrchid;
                    break;
                case "OhShit":
                    textcolor = Color.OrangeRed;
                    break;
                case "Mana":
                    textcolor = Color.DodgerBlue;
                    break;
                case "DPS":
                    textcolor = Color.Plum;
                    break;
                case "Utility":
                    textcolor = Color.SandyBrown;
                    break;
                default:
                    textcolor = Color.Black;
                    break;
            }
            slog(textcolor, reason + ": casting {0} on {1} at distance {2} with type {3} at hp {4}", spell, privacyname(target), Round(target.Distance), type, Round(target.HealthPercent));
        }
        private string privacyname(WoWUnit unit)
        {
            string name;
            if (unit == Me)
            {
                return "Myself";
            }
            else if (unit == tank)
            {
                return "Tank " + tank.Name[0] + tank.Name[1] + "****";
            }
            else if (unit is WoWPlayer)
            {
                name = unit.Class.ToString() + " " + unit.Name[0] + unit.Name[1] + "****";
                return name;
            }
            else return unit.Name;
        }
        private void formatslog(string type, string reason, string spell)
        {
            formatslog(type, reason, spell, Me);
        }
        #endregion
        #region Cluster
        public enum ClusterType { Radius, Chained, Cone }
        public static class Unit
        {
            /// <summary>
            ///   Gets the nearby friendly players within 40 yards.
            /// </summary>
            /// <value>The nearby friendly players.</value>
            public static List<WoWPlayer> NearbyFriendlyPlayers
            {
                get
                {
                    return ObjectManager.GetObjectsOfType<WoWPlayer>(true, true).Where(p => p.DistanceSqr <= 40 * 40 && p.IsFriendly).ToList();
                }
            }

            /// <summary>
            ///   Gets the nearby unfriendly units within 40 yards.
            /// </summary>
            /// <value>The nearby unfriendly units.</value>
            public static List<WoWPlayer> NearbyUnfriendlyUnits
            {
                get
                {
                    return ObjectManager.GetObjectsOfType<WoWPlayer>(false, false).Where(p => p.IsHostile && !p.Dead && !p.IsPet && p.DistanceSqr <= 40 * 40).ToList();
                }
            }

            public static bool HasAura(WoWPlayer unit, string aura)
            {
                return HasAura(unit, aura, 0);
            }

            public static bool HasAura(WoWPlayer unit, string aura, int stacks)
            {
                var auras = unit.GetAllAuras();
                foreach (var a in auras)
                {
                    Logging.WriteDebug("Aura name: " + a.Name + " - " + a.StackCount);
                    if (a.Name == aura)
                        return a.StackCount >= stacks;
                }
                return false;
            }

            public static bool HasAnyAura(WoWPlayer unit, params string[] auraNames)
            {
                var auras = unit.GetAllAuras();
                var hashes = new HashSet<string>(auraNames);
                return auras.Any(a => hashes.Contains(a.Name));
            }

            public static bool HasAuraWithMechanic(WoWPlayer unit, params WoWSpellMechanic[] mechanics)
            {
                var auras = unit.GetAllAuras();
                return auras.Any(a => mechanics.Contains(a.Spell.Mechanic));
            }
        }
        public static class Clusters
        {
            public static int GetClusterCount(WoWPlayer target, IEnumerable<WoWPlayer> otherUnits, ClusterType type, float clusterRange)
            {
                switch (type)
                {
                    case ClusterType.Radius:
                        return GetRadiusClusterCount(target, otherUnits, clusterRange);
                    case ClusterType.Chained:
                        return GetChainedClusterCount(target, otherUnits, clusterRange);
                    case ClusterType.Cone:
                        return GetConeClusterCount(target, otherUnits, clusterRange);
                    default:
                        throw new ArgumentOutOfRangeException("type");
                }
            }

            public static WoWPlayer GetBestUnitForCluster(IEnumerable<WoWPlayer> units, ClusterType type, float clusterRange)
            {
                switch (type)
                {
                    case ClusterType.Radius:
                        return (from u in units
                                select new { Count = GetRadiusClusterCount(u, units, clusterRange), Unit = u }).OrderByDescending(a => a.Count).
                            FirstOrDefault().Unit;
                    case ClusterType.Chained:
                        return (from u in units
                                select new { Count = GetChainedClusterCount(u, units, clusterRange), Unit = u }).OrderByDescending(a => a.Count).
                            FirstOrDefault().Unit;
                    // coned doesn't have a best unit, since we are the source
                    default:
                        throw new ArgumentOutOfRangeException("type");
                }
            }

            private static int GetConeClusterCount(WoWPlayer target, IEnumerable<WoWPlayer> otherUnits, float distance)
            {
                var targetLoc = target.Location;
                return otherUnits.Count(u => target.IsSafelyFacing(u, 90f) && u.Location.Distance(targetLoc) <= distance); // most (if not all) cone spells are 90 degrees
            }

            private static int GetRadiusClusterCount(WoWPlayer target, IEnumerable<WoWPlayer> otherUnits, float radius)
            {
                var targetLoc = target.Location;
                return otherUnits.Count(u => u.Location.DistanceSqr(targetLoc) <= radius * radius);
            }

            private static int GetChainedClusterCount(WoWPlayer target, IEnumerable<WoWPlayer> otherUnits, float chainRange)
            {
                var unitCounters = otherUnits.Select(u => GetUnitsChainWillJumpTo(target, otherUnits.ToList(), chainRange).Count);

                return unitCounters.Max() + 1;
            }

            private static List<WoWPlayer> GetUnitsChainWillJumpTo(WoWPlayer target, List<WoWPlayer> otherUnits, float chainRange)
            {
                var targetLoc = target.Location;
                var targetGuid = target.Guid;
                for (int i = otherUnits.Count - 1; i >= 0; i--)
                {
                    if (otherUnits[i].Guid == targetGuid || otherUnits[i].Location.DistanceSqr(targetLoc) > chainRange)
                        otherUnits.RemoveAt(i);
                }
                return otherUnits;
            }
        }
        #endregion
        #endregion
    }
}
